package cn.bonoon.kernel.web.views;

import java.io.StringWriter;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.runtime.resource.loader.StringResourceLoader;
import org.apache.velocity.runtime.resource.util.StringResourceRepository;
import org.apache.velocity.tools.view.context.ChainedContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContextException;
import org.springframework.web.servlet.view.AbstractView;
import org.springframework.web.servlet.view.velocity.VelocityConfig;
import org.springframework.web.util.NestedServletException;

@SuppressWarnings("deprecation")
public class TemplateView extends AbstractView implements InitializingBean{
	private static final String LOADER = "resource.loader";
	private static final String LOADER_CLASS = "string.resource.loader.class";
	private static final String STRING_LOADER = "string";
	public static final String DEFAULT_LAYOUT_URL = "layout.vm";
	public static final String DEFAULT_LAYOUT_KEY = "layout";
	public static final String DEFAULT_SCREEN_CONTENT_KEY = "screen_content";
	private String screenContentKey = DEFAULT_SCREEN_CONTENT_KEY;
	private String layoutUrl = DEFAULT_LAYOUT_URL;
	private String layoutKey = DEFAULT_LAYOUT_KEY;

	private String encoding;
	private VelocityEngine velocityEngine;
	private Class<?> viewClass;
	public final Class<?> getViewClass() {
		return viewClass;
	}

	public final void setViewClass(Class<?> viewClass) {
		this.viewClass = viewClass;
	}

	public final void setViewClass(Class<?> viewClass, TemplateType viewType) {
		this.viewClass = viewClass;
		this.viewType = viewType;
	}
	
	private TemplateType viewType = TemplateType.DETAIL;
	
	public enum TemplateType{
		DETAIL, CREATOR, EDITOR
	}

	protected String getViewName() {
		return viewClass.getName();
	}

	protected String getTemplateString() {
		if(viewType == TemplateType.CREATOR){
			
		}
		
		return "";
	}

	@Override
	protected void renderMergedOutputModel(Map<String, Object> model,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {

		applyContentType(response);

		renderMergedTemplateModel(model, request, response);
	}

	protected void applyContentType(HttpServletResponse response)	{
		if (response.getContentType() == null) {
			response.setContentType(getContentType());
		}
	}
	
	protected void renderMergedTemplateModel(
			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

		Context velocityContext = createVelocityContext(model, request, response);

		doRender(velocityContext, response);
	}
	protected void doRender(Context context, HttpServletResponse response) throws Exception {
		renderScreenContent(context);

		// Velocity context now includes any mappings that were defined
		// (via #set) in screen content template.
		// The screen template can overrule the layout by doing
		// #set( $layout = "MyLayout.vm" )
		String layoutUrlToUse = (String) context.get(this.layoutKey);
		if (layoutUrlToUse != null) {
			if (logger.isDebugEnabled()) {
				logger.debug("Screen content template has requested layout [" + layoutUrlToUse + "]");
			}
		}
		else {
			// No explicit layout URL given -> use default layout of this view.
			layoutUrlToUse = this.layoutUrl;
		}

		mergeTemplate(getTemplate(layoutUrlToUse), context, response);
	}
	
	protected void mergeTemplate(
			Template template, Context context, HttpServletResponse response) throws Exception {

		try {
			template.merge(context, response.getWriter());
		}
		catch (MethodInvocationException ex) {
			Throwable cause = ex.getWrappedThrowable();
			throw new NestedServletException(
					"Method invocation failed during rendering of Velocity view with name '" +
					getBeanName() + "': " + ex.getMessage() + "; reference [" + ex.getReferenceName() +
					"], method '" + ex.getMethodName() + "'",
					cause==null ? ex : cause);
		}
	}

	private void renderScreenContent(Context velocityContext) throws Exception {

		String vName = getViewName();
		StringResourceRepository srr = StringResourceLoader.getRepository();
		if (null == srr.getStringResource(vName)) {
			srr.putStringResource(getViewName(), getTemplateString());
		}

		StringWriter sw = new StringWriter();
		Template screenContentTemplate = getTemplate(vName);
		screenContentTemplate.merge(velocityContext, sw);

		// Put rendered content into Velocity context.
		velocityContext.put(this.screenContentKey, sw.toString());
	}

	protected Context createVelocityContext(Map<String, Object> model,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		ChainedContext velocityContext = new ChainedContext(
				new VelocityContext(model), getVelocityEngine(), request,
				response, getServletContext());
		return velocityContext;
	}

	public final String getScreenContentKey() {
		return screenContentKey;
	}

	public final void setScreenContentKey(String screenContentKey) {
		this.screenContentKey = screenContentKey;
	}

	public final String getLayoutUrl() {
		return layoutUrl;
	}

	public final void setLayoutUrl(String layoutUrl) {
		this.layoutUrl = layoutUrl;
	}

	public final String getLayoutKey() {
		return layoutKey;
	}

	public final void setLayoutKey(String layoutKey) {
		this.layoutKey = layoutKey;
	}

	public final String getEncoding() {
		return encoding;
	}

	public final void setEncoding(String encoding) {
		this.encoding = encoding;
	}

	public final VelocityEngine getVelocityEngine() {
		return velocityEngine;
	}

	public final void setVelocityEngine(VelocityEngine velocityEngine) {
		this.velocityEngine = velocityEngine;
	}

	@Override
	protected void initApplicationContext() throws BeansException {
		super.initApplicationContext();

		if (getVelocityEngine() == null) {
			// No explicit VelocityEngine: try to autodetect one.
			setVelocityEngine(autodetectVelocityEngine());
			getVelocityEngine().setProperty(LOADER, STRING_LOADER);
			getVelocityEngine().setProperty(LOADER_CLASS,
					StringResourceLoader.class.getName());
		}
	}

	protected VelocityEngine autodetectVelocityEngine() throws BeansException {
		try {
			VelocityConfig velocityConfig = BeanFactoryUtils
					.beanOfTypeIncludingAncestors(getApplicationContext(),
							VelocityConfig.class, true, false);
			return velocityConfig.getVelocityEngine();
		} catch (NoSuchBeanDefinitionException ex) {
			throw new ApplicationContextException(
					"Must define a single VelocityConfig bean in this web application context "
							+ "(may be inherited): VelocityConfigurer is the usual implementation. "
							+ "This bean may be given any name.", ex);
		}
	}

	protected Template getTemplate(String name) throws Exception {
		return (getEncoding() != null ? getVelocityEngine().getTemplate(name,
				getEncoding()) : getVelocityEngine().getTemplate(name));
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		if(null == viewClass)
			throw new IllegalArgumentException("Property 'viewClass' is required");
	}
}
